package etcd_sdr

import (
	"context"
	"fmt"
	clientv3 "go.etcd.io/etcd/client/v3"
	"go.uber.org/zap"
	"time"
)

type Service struct {
	ttl           int64 // ttl seconds
	lastUpdated   int64
	leaseID       clientv3.LeaseID
	ctx           context.Context
	cancelFunc    context.CancelFunc
	client        *clientv3.Client
	keepAliveChan <-chan *clientv3.LeaseKeepAliveResponse
	info          *ServerInfo
	log           *zap.Logger
}

/*
NewService new service
ttl: seconds
*/
func NewService(ctx context.Context, cancelFunc context.CancelFunc, ttl int64, client *clientv3.Client, info *ServerInfo, log *zap.Logger) Service {
	return Service{
		ttl:        ttl,
		ctx:        ctx,
		cancelFunc: cancelFunc,
		client:     client,
		info:       info,
		log:        log,
	}
}

func (s *Service) setLeaseGrant() error {
	grant, err := s.client.Grant(s.ctx, s.ttl)
	if err != nil {
		return err
	}

	s.ttl = grant.TTL
	s.leaseID = grant.ID

	return nil
}

func (s *Service) listenKeepAlive() {
	for {
		select {
		case <-s.keepAliveChan:
			// update service lastUpdated time
			s.lastUpdated = time.Now().UnixMilli()

		case <-s.ctx.Done():
			// update
			s.log.Info(fmt.Sprintf("[-] %s service closed", s.info.GetServerInfoKey()))
			return
		}
	}
}

func (s *Service) RegisterService() (err error) {
	s.log.Info(fmt.Sprintf("[+]sdr: register service ID:%s endpoint:%s \n", s.info.ID, s.info.Target.Endpoint()))
	if err = s.setLeaseGrant(); err != nil {
		s.log.Error("[-]sdr: register service set lease err")
		return err
	}

	key := s.info.GetServerInfoKey()
	var _info []byte
	if _info, err = MarshalServerInfo(s.info); err != nil {
		return err
	}

	_, err = s.client.Put(s.ctx, key, string(_info), clientv3.WithLease(s.leaseID))
	if err != nil {
		return err
	}

	// keepAlive
	s.keepAliveChan, err = s.client.KeepAlive(s.ctx, s.leaseID)
	if err != nil {
		return err
	}

	go s.listenKeepAlive()

	return nil
}

// UnRegisterService un register service and stop keepalive
func (s *Service) UnRegisterService() {
	if s.client == nil {
		fmt.Println("un re client is null")
	}
	_, err := s.client.Revoke(s.ctx, s.leaseID)
	if err != nil {
		s.log.Error(err.Error())
		return
	}

	// cancel service context, and this service`s keepAliveChan will close
	s.cancelFunc()
	s.log.Info(fmt.Sprintf("[-] %s service closed", s.info.GetServerInfoKey()))
}
